import java.io.BufferedReader;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;

/*
 * Class Lexer
 * 
 * Update the method nextToken() such to the provided
 * specifications of the Decaf Programming Language.
 * 
 * You are not allowed to use any built it in tokenizer
 * in Java. You are only allowed to scan the input file
 * one character at a time.
 */

public class Lexer {

	private BufferedReader reader; // Reader
	private char curr; // The current character being scanned
	private String bufferingString;
	private String bufferingNumber;
	private boolean inComment;
	private boolean buffereingPoint;
	private boolean currentlyBufferingString;
	
	private static final char EOF = (char) (-1);
        // End of file character

	public Lexer(String file) {
	        try {
	                reader = new BufferedReader(new FileReader(file));
	        } catch (FileNotFoundException e) {
	                e.printStackTrace();
	        }
	
	        // Read the first character
	        curr = read();
	}
	
	private char read() {
	        try {
	                return (char) (reader.read());
	        } catch (IOException e) {
	                e.printStackTrace();
	                return EOF;
	        }
	}
	
	// Checks if a character is a digit
	private boolean isNumeric(char c) {
	        if (c >= '0' && c <= '9')
	                return true;
	
	        return false;
	}
	
	private Token getMatchingToken(String s){
			bufferingString ="";
			if (s.equals("==")){
			        return new Token("EQ ", s);
			}else if (s.equals("||")){
			        return new Token("LO", s);
			} else if (s.equals("!=")){
			        return new Token("NE", s);
			}       else if (s.equals("&&")){
			        return new Token("LA", s);
			}       else if (s.equals("class")){
			        return new Token("KW", s);
			} else if (s.equals("else")){
			        return new Token("KW", s);
			}       else if (s.equals("if")){
			        return new Token("KW", s);
			}       else if (s.equals("int")){
			        return new Token("KW", s);
			} else if (s.equals("float")){
			        return new Token("KW", s);
			}       else if (s.equals("boolean")){
			        return new Token("KW", s);
			}       else if (s.equals("String")){
			        return new Token("KW", s);
			} else if (s.equals("return")){
			        return new Token("KW", s);
			}       else if (s.equals("static")){
			        return new Token("KW", s);
			}       else if (s.equals("while")){
			        return new Token("KW", s);
			} else if (s.equals("true")){
			        return new Token("BL", s);
			}       else if (s.equals("false")){
			        return new Token("BL", s);
			} else {
			        return new Token("ID", s);
			}
	}
	
	private boolean isTerminal(char c){
 		switch (c){
 			case ' ': // Whitespaces
 			case '\f':
 			case '\r':
 			case '\b':
 			case '\t':
 			case '\n':
 			case ';':
				return true;
 			default: 
				return false;
 		}
	}
	
	private boolean isAlreadyDefined(char c){
		switch (c){
			case '+':
			case '-':
			case '*':
			case '/':
			case ',':
			case '(':
			case ')':
			case '{':
			case '}':
			case '%':
			case '"':
				return true;
			default: 
				return false;
										}
	}
	
	public Token nextToken() {
	
		int state = 1; // Initial state
		int numBuffer = 0; // A buffer for number literals
		
		while (true) {
			if (curr == EOF) {
				try {
					if(!bufferingString.equals("")){
						return getMatchingToken(bufferingString);
					}
					reader.close();
				} catch (IOException e) {
					e.printStackTrace();
				}
				return null;
			}
		        
			switch (state) {
				// Controller
				case 1:
					switch (curr) {
						case ' ': // Whitespaces
						case '\f':
						case '\r':
						case '\b':
						case '\t':
						case '\n':
							curr = read();
							continue;
						
						case ';':
							curr = read();
							return new Token("SM", ";");
						
						case '+':
							curr = read();
							return new Token("PO", "+");
						
						case '-':
							curr = read();
							return new Token("MO", "-");
						
						case '*':
							curr = read();
							return new Token("TO", "*");
						
						case '/':
							curr = read();
							if( curr == '/'){
								state = 7;
								continue;
							}else if (curr == '*'){
								state = 8;
								inComment = true;
								curr = read();
								continue;
							}else{
								return new Token("DO", "/");
							}
							        
						case ',':
							curr = read();
							return new Token("FA", ",");
						        
						case '(':
							curr = read();
							return new Token("LP", "(");
						
						case ')':
							curr = read();
							return new Token("RP", ")");
						
						case '{':
							curr = read();
							return new Token("LB", "}");
						
						case '}':
							curr = read();
							return new Token("RB", "}");
						
						case '%':
							curr = read();
							return new Token("MD", "%");
						        
						case '"':
							curr = read();
							state = 6;
							bufferingString= "";
							currentlyBufferingString = true;
							continue;
							
						case '?':
							curr = read();
							return new Token("ERROR", "Invalid input:"+ curr);
						default:
							state = 2;
							continue;
						}
		
						// Integer - Start
		        case 2:
							if (isNumeric(curr)) {
							        numBuffer = 0; // Reset the buffer.
							        numBuffer += (curr - '0');
							
							        state = 3;
							        curr = read();
							} else {
							        state = 4;
							}
							continue;
							
							// Integer - Body
		        case 3:
							if (isNumeric(curr)) {
							        numBuffer *= 10;
							        numBuffer += (curr - '0');
							        curr = read();
							} else {
							        if(curr == '.'){
							                bufferingNumber = numBuffer+".";
							                buffereingPoint = true;
							                curr = read();
							                if(isNumeric(curr)){
							                        numBuffer = 0;
							                }else{
							                        buffereingPoint = false;
							                        return new Token("Error", "Invalid input: "+bufferingNumber);
							                }
							                continue;
							        }
							        if(buffereingPoint){
							                buffereingPoint = false;
							                return new Token("NM", bufferingNumber+""+numBuffer);
							        }else   
							                return new Token("NM", "" + numBuffer);
							}
							continue;
		        case 4:
							bufferingString = curr+"";
							curr = read();
							state = 5;
		        case 5:
							if (!isTerminal(curr) && !isAlreadyDefined(curr)) {
							        bufferingString += curr+"";
							        curr = read();
							} else {
							        return getMatchingToken(bufferingString);
							}
							continue;
		        case 6:
							bufferingString += curr;
							curr = read();
							if(curr == '"'){
							        return new Token("ST", bufferingString);
							}else if(curr == '\n'){
							        return new Token("ERROR", "Invalid String Literal");
							}
							continue;
		        case 7:
							while (curr != '\n'){
							        curr = read();
							}
							state = 1;
							continue;
		        case 8:
							while (inComment){
							        if (curr == '*'){
							                curr = read();
							                if( curr == '/'){
							                        inComment= false;
							                        state =1;
							                }
							        }
							        curr = read();
							        continue;
							}
		      }
			}
  	}
}